package com.acmen.easyapi.dbcp;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.acmen.easyapi.util.HumpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.util.LinkedCaseInsensitiveMap;

import java.lang.reflect.*;
import java.math.BigDecimal;
import java.sql.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;

/**
 * @Author acmen
 * 数据库记录转JAVA对象
 */
public class JavaObjectRowMapper implements RowMapper {
    private static Map<Class,JavaObjectRowMapper> javaObjectRowMapperMap = new HashMap<>();
    private static final Logger log = LoggerFactory.getLogger(JavaObjectRowMapper.class);
    private Class entityType;
    private Map<String, Field> fieldMap = new LinkedCaseInsensitiveMap();


    public static JavaObjectRowMapper getInstance(Class entityType){
        if(javaObjectRowMapperMap.containsKey(entityType)){
            return javaObjectRowMapperMap.get(entityType);
        }else{
            JavaObjectRowMapper javaObjectRowMapper = new JavaObjectRowMapper(entityType) ;
            javaObjectRowMapperMap.put(entityType, javaObjectRowMapper);
            return javaObjectRowMapper;
        }
    }

    public JavaObjectRowMapper(Class entityType) {
        this.entityType = entityType;
        for(Field field : getFields(entityType)){
            fieldMap.put(field.getName(),field);
        }
    }

    @Override
    public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
        Object instance;
        try {
            instance = entityType.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
            return null;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            return null;
        }

        ResultSetMetaData rsmd = rs.getMetaData();
        int columnCount = rsmd.getColumnCount();
        for (int i = 1; i <= columnCount; i++) {
            String column = JdbcUtils.lookupColumnName(rsmd, i);
            Field field = fieldMap.get(HumpUtil.underline2hump(column));
            if(field==null){
                log.debug("数据库表列"+column+"无法找到对应的Jvava类字段");
                continue;
            }
            String firstLetter = field.getName().substring(0, 1).toUpperCase();
            String setter = "set" + firstLetter + field.getName().substring(1);
            if(field==null){
                continue;
            }
            Object value;
            try{
                value = getResultSetValue(rs, i,field.getGenericType());
            }catch (Exception e){
                log.error("数据库表列"+column+"的值'"+rs.getObject(i)+"'无法映射成Jvava类字段"+field.getName()+"("+field.getType().getName()+")",e);
                continue;
            }
            if(value==null){
                continue;
            }
            try {
                Method method = entityType.getDeclaredMethod(setter,field.getType());
                method.invoke(instance,value);
            } catch (NoSuchMethodException e) {
                log.error(e.getMessage());
                boolean accessible = field.isAccessible();
                try{
                    if(!accessible){
                        field.setAccessible(true);
                    }
                    field.set(instance,value);
                } catch (IllegalAccessException ex) {
                    ex.printStackTrace();
                } finally {
                    if(!accessible){
                        field.setAccessible(false);
                    }
                }
            }catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
        return instance;
    }

    public static Object getResultSetValue(ResultSet rs, int index, Type fieldType) throws SQLException {
        Object obj = rs.getObject(index);
        if(obj==null){
            return null;
        }
        if(fieldType.equals(Integer.class)||"int".equals(fieldType.getTypeName())){
            obj = rs.getInt(index);
        }else if(fieldType.equals(Long.class)||"long".equals(fieldType.getTypeName())){
            obj = rs.getLong(index);
        }else if(fieldType.equals(Float.class)||"float".equals(fieldType.getTypeName())){
            obj = rs.getFloat(index);
        }else if(fieldType.equals(Double.class)||"double".equals(fieldType.getTypeName())){
            obj = rs.getDouble(index);
        }else if(fieldType.equals(Short.class)||"short".equals(fieldType.getTypeName())){
            obj = rs.getShort(index);
        }else if(fieldType.equals(Boolean.class)||"boolean".equals(fieldType.getTypeName())){
            obj = rs.getBoolean(index);
        }else if(fieldType.equals(BigDecimal.class)){
            obj = rs.getBigDecimal(index);
        }else if(fieldType.equals(String.class)){
            obj = rs.getString(index);
        }else if(fieldType.equals(LocalDateTime.class)){
            obj = rs.getTimestamp(index).toLocalDateTime();
        }else if(fieldType.equals(LocalDate.class)){
            obj = rs.getDate(index).toLocalDate();
        }else if(fieldType.equals(LocalTime.class)){
            obj = rs.getTime(index).toLocalTime();
        }else if(fieldType.equals(java.util.Date.class)){
            obj = new java.util.Date(rs.getTimestamp(index).getTime());
        }else if(fieldType.equals(Time.class)){
            obj = rs.getTime(index);
        }else if(fieldType.equals(Timestamp.class)){
            obj = rs.getTimestamp(index);
        }else if(fieldType.equals(JSONObject.class)){
            try {
                obj = JSON.parseObject(rs.getString(index));
            }catch (Exception e){
                log.error("转换类型JSONObject异常：{}",e);
            }
        }else if(obj instanceof String && !fieldType.equals(String.class)){
            if(fieldType instanceof Class){
                try {
                    obj = JSON.parseObject(rs.getString(index),fieldType);
                }catch (Exception e){
                    log.error("转换类型"+fieldType.getTypeName()+"异常：{}",e);
                }
            }else if(fieldType instanceof ParameterizedType) {
                //ParameterizedType表示一种参数化的类型 List list = new ArrayList<String>();
                ParameterizedType parameterizedType = (ParameterizedType) fieldType;
                //例如List<String> ids,rawType为List
                Type rawType = parameterizedType.getRawType();
                Class  clazz = (Class) rawType;
                if (Collection.class.isAssignableFrom(clazz)) {
                    Type paramType = parameterizedType.getActualTypeArguments()[0];
                    try {
                        if(paramType instanceof Class){
                            obj = JSON.parseArray(rs.getString(index),(Class)paramType);
                        }else{
                            obj = JSON.parseObject(rs.getString(index),Collection.class);
                        }
                    }catch (Exception e){
                        log.error("转换类型"+paramType.getTypeName()+"异常：{}",e);
                    }
                }  else {
                    try {
                        obj = JSON.parseObject(rs.getString(index),rawType);
                    }catch (Exception e){
                        log.error("转换类型"+rawType.getTypeName()+"异常：{}",e);
                    }
                }
            }
        }  else{
            if (obj instanceof Blob) {
                Blob blob = (Blob) obj;
                obj = blob.getBytes(1, (int) blob.length());
            } else if (obj instanceof Clob) {
                Clob clob = (Clob) obj;
                obj = clob.getSubString(1, (int) clob.length());
            }
        }
        return obj;
    }
    /**
     * @param clazz
     * @return
     */
    public static List<Field> getFields(Class clazz) {
        List<Field> fieldList = new ArrayList<Field>();
        Field[] fields = clazz.getDeclaredFields();
        fields:
        for (Field field : fields) {
            for (Field f : fieldList) {
                if (f.getName().equals(field.getName())) {
                    continue fields;
                }
            }
            fieldList.add(field);
        }
        Class<?> superclass = clazz.getSuperclass();
        if (superclass != null && superclass != Object.class) {
            List<Field> tempList = getFields(superclass);
            tempList:
            for (Field field : tempList) {
                for (Field f : fieldList) {
                    if (f.getName().equals(field.getName())) {
                        continue tempList;
                    }
                }
                fieldList.add(field);
            }
        }
        return fieldList;
    }
}
